

#include "stdafx.h"
#include "pf_ellipse.h"

#include "pf_graphic.h"
#include "pf_graphicview.h"
#include "pf_painter.h"
#include "pf_information.h"

/**
 * Constructor.
 */
PF_Ellipse::PF_Ellipse(PF_Container* parent,
                       const PF_EllipseAttribute& d)
        :PF_AtomicEntity(parent), data(d) {

    //calculateEndpoints();
    calculateBorders();
}


/**
 * Recalculates the endpoints using the angles and the radius.
 */
 /*
void PF_Ellipse::calculateEndpoints() {
    double angle = data.majorP.angle();
    double radius1 = getMajorRadius();
    double radius2 = getMinorRadius();

    startpoint.set(data.center.x + cos(data.angle1) * radius1,
                   data.center.y + sin(data.angle1) * radius2);
    startpoint.rotate(data.center, angle);
    endpoint.set(data.center.x + cos(data.angle2) * radius1,
                 data.center.y + sin(data.angle2) * radius2);
    endpoint.rotate(data.center, angle);
}
*/


/**
 * Calculates the boundary box of this ellipse.
 *
 * @todo Fix that - the algorithm used is really bad / slow.
 */
void PF_Ellipse::calculateBorders() {
	PF_DEBUG->print("PF_Ellipse::calculateBorders");

    double radius1 = getMajorRadius();
    double radius2 = getMinorRadius();
    double angle = getAngle();
    double a1 = ((!isReversed()) ? data.angle1 : data.angle2);
    double a2 = ((!isReversed()) ? data.angle2 : data.angle1);
	PF_Vector startpoint = getStartpoint();
	PF_Vector endpoint = getEndpoint();

    double minX = std::min(startpoint.x, endpoint.x);
    double minY = std::min(startpoint.y, endpoint.y);
    double maxX = std::max(startpoint.x, endpoint.x);
    double maxY = std::max(startpoint.y, endpoint.y);

    // kind of a brute force. TODO: exact calculation
    PF_Vector vp;
	double a = a1;
	do {
        vp.set(data.center.x + radius1 * cos(a),
               data.center.y + radius2 * sin(a));
        vp.rotate(data.center, angle);

        minX = std::min(minX, vp.x);
        minY = std::min(minY, vp.y);
        maxX = std::max(maxX, vp.x);
        maxY = std::max(maxY, vp.y);

		a += 0.03;
    } while (PF_Math::isAngleBetween(PF_Math::correctAngle(a), a1, a2, false) && 
			a<4*M_PI);


    minV.set(minX, minY);
    maxV.set(maxX, maxY);
	PF_DEBUG->print("PF_Ellipse::calculateBorders: OK");
}



PF_VectorSolutions PF_Ellipse::getRefPoints() {
    PF_VectorSolutions ret(getStartpoint(), getEndpoint(), data.center);
    return ret;
}



PF_Vector PF_Ellipse::getNearestEndpoint(const PF_Vector& coord, double* dist) {
    double dist1, dist2;
    PF_Vector nearerPoint;
	PF_Vector startpoint = getStartpoint();
	PF_Vector endpoint = getEndpoint();

    dist1 = startpoint.distanceTo(coord);
    dist2 = endpoint.distanceTo(coord);

    if (dist2<dist1) {
        if (dist!=NULL) {
            *dist = dist2;
        }
        nearerPoint = endpoint;
    } else {
        if (dist!=NULL) {
            *dist = dist1;
        }
        nearerPoint = startpoint;
    }

    return nearerPoint;
}



PF_Vector PF_Ellipse::getNearestPointOnEntity(const PF_Vector& coord,
        bool onEntity, double* dist, PF_Entity** entity) {
	
    PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity");
    
    PF_Vector ret(false);
    
    if (entity!=NULL) {
        *entity = this;
    }
    double ang = getAngle();

    PF_Vector normalized = (coord - data.center).rotate(-ang);

    double dU = normalized.x;
    double dV = normalized.y;
    double dA = getMajorRadius();
    double dB = getMinorRadius();
    double dEpsilon = 1.0e-8;
    int iMax = 32;
    int riIFinal = 0;
    double rdX = 0.0;
    double rdY = 0.0;
    double dDistance; 
    bool swap = false;
    bool majorSwap = false;

    if (dA<dB) {
        double dum = dA;
        dA = dB;
        dB = dum;
        dum = dU;
        dU = dV;
        dV = dum;
        majorSwap = true;
    }

    if (dV<0.0) {
        dV*=-1.0;
        swap = true;
    }
    
    // initial guess 
    double dT = dB*(dV - dB); 
    
    // Newton s method
    int i; 
    for (i = 0; i < iMax; i++) { 
        PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: i: %d", i);
        double dTpASqr = dT + dA*dA; 
        double dTpBSqr = dT + dB*dB; 
        double dInvTpASqr = 1.0/dTpASqr; 
        double dInvTpBSqr = 1.0/dTpBSqr; 
        double dXDivA = dA*dU*dInvTpASqr; 
        double dYDivB = dB*dV*dInvTpBSqr; 
        double dXDivASqr = dXDivA*dXDivA; 
        double dYDivBSqr = dYDivB*dYDivB; 
        double dF = dXDivASqr + dYDivBSqr - 1.0; 
        PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: dF: %f", dF);
        if ( fabs(dF) < dEpsilon ) { 
            // F(t0) is close enough to zero, terminate the iteration:
            rdX = dXDivA*dA;
            rdY = dYDivB*dB; 
            riIFinal = i; 
            PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: rdX,rdY 1: %f,%f", rdX, rdY);
            break; 
        } 
        double dFDer = 2.0*(dXDivASqr*dInvTpASqr + dYDivBSqr*dInvTpBSqr); 
        double dRatio = dF/dFDer; 
        PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: dRatio: %f", dRatio);
        if ( fabs(dRatio) < dEpsilon ) { 
            // t1-t0 is close enough to zero, terminate the iteration:
            rdX = dXDivA*dA; 
            rdY = dYDivB*dB; 
            riIFinal = i; 
            PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: rdX,rdY 2: %f,%f", rdX, rdY);
            break; 
        } 
        dT += dRatio; 
    } 
    if ( i == iMax ) { 
        // failed to converge:
        PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: failed");
        dDistance = LX_DOUBLEMAX;
    } 
    else {
        double dDelta0 = rdX - dU;
        double dDelta1 = rdY - dV; 
        dDistance = sqrt(dDelta0*dDelta0 + dDelta1*dDelta1); 
        ret = PF_Vector(rdX, rdY);
        PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: rdX,rdY 2: %f,%f", rdX, rdY);
        PF_DEBUG->print("PF_Ellipse::getNearestPointOnEntity: ret: %f,%f", ret.x, ret.y);
    }
    
    if (dist!=NULL) {
        if (ret.valid) {
            *dist = dDistance;
        } else {
            *dist = LX_DOUBLEMAX;
        }
    }
   
    if (ret.valid) {
        if (swap) {
            ret.y*=-1.0;
        }
        if (majorSwap) {
            double dum = ret.x;
            ret.x = ret.y;
            ret.y = dum;
        }
        ret = (ret.rotate(ang) + data.center);
        
        if (onEntity) {
            double a1 = data.center.angleTo(getStartpoint());
            double a2 = data.center.angleTo(getEndpoint());
            double a = data.center.angleTo(ret);
            if (!PF_Math::isAngleBetween(a, a1, a2, data.reversed)) {
                ret = PF_Vector(false);
            }
        }
    }
    
    return ret;
}



/**
 * @param tolerance Tolerance.
 *
 * @retval true if the given point is on this entity.
 * @retval false otherwise
 */
bool PF_Ellipse::isPointOnEntity(const PF_Vector& coord,
                                double tolerance) {
    double dist = getDistanceToPoint(coord, NULL, LX_Define::ResolveNone);
    return (dist<=tolerance);
}



PF_Vector PF_Ellipse::getNearestCenter(const PF_Vector& coord,
                                       double* dist) {
    if (dist!=NULL) {
        *dist = coord.distanceTo(data.center);
    }
    return data.center;
}



/**
 * @todo Implement this.
 */
PF_Vector PF_Ellipse::getNearestMiddle(const PF_Vector& /*coord*/,
                                       double* dist) {
    if (dist!=NULL) {
        *dist = LX_DOUBLEMAX;
    }
    return PF_Vector(false);
}



PF_Vector PF_Ellipse::getNearestDist(double /*distance*/,
                                     const PF_Vector& /*coord*/,
                                     double* dist) {
    if (dist!=NULL) {
        *dist = LX_DOUBLEMAX;
    }
    return PF_Vector(false);
}



double PF_Ellipse::getDistanceToPoint(const PF_Vector& coord,
                                      PF_Entity** entity,
                                      LX_Define::ResolveLevel, double /*solidDist*/) {
    double dist = LX_DOUBLEMAX;
    getNearestPointOnEntity(coord, true, &dist, entity);

    //   6 Jan 2011 : Add selection by center point
    float dToCenter=data.center.distanceTo(coord);

    if (dist<dToCenter) {
        return dist;
    } else {
        return dToCenter;
    }

}



void PF_Ellipse::move(PF_Vector offset) {
    data.center.move(offset);
    //calculateEndpoints();
    calculateBorders();
}



void PF_Ellipse::rotate(PF_Vector center, double angle) {
    data.center.rotate(center, angle);
    data.majorP.rotate(angle);
    //calculateEndpoints();
    calculateBorders();
}



void PF_Ellipse::moveStartpoint(const PF_Vector& pos) {
	data.angle1 = getEllipseAngle(pos);
	//data.angle1 = data.center.angleTo(pos);
	//calculateEndpoints();
	calculateBorders();
}



void PF_Ellipse::moveEndpoint(const PF_Vector& pos) {
	data.angle2 = getEllipseAngle(pos);
	//data.angle2 = data.center.angleTo(pos);
	//calculateEndpoints();
	calculateBorders();
}


LX_Define::Ending PF_Ellipse::getTrimPoint(const PF_Vector& coord, 
		const PF_Vector& trimPoint) {
	
	double angEl = getEllipseAngle(trimPoint);
	double angM = getEllipseAngle(coord);

	if (PF_Math::getAngleDifference(angM, angEl)>M_PI) {
		//if (data.reversed) {
		//	return LX_Define::EndingEnd;
		//}
		//else {
			return LX_Define::EndingStart;
		//}
	}
	else {
		//if (data.reversed) {
		//	return LX_Define::EndingStart;
		//}
		//else {
			return LX_Define::EndingEnd;
		//}
	}
}

double PF_Ellipse::getEllipseAngle(const PF_Vector& pos) {
	PF_Vector m = pos;
	m.rotate(data.center, -data.majorP.angle());
	PF_Vector v = m-data.center;
	v.scale(PF_Vector(1.0, 1.0/data.ratio));
	return v.angle(); 
}



void PF_Ellipse::scale(PF_Vector center, PF_Vector factor) {
    data.center.scale(center, factor);
    data.majorP.scale(factor);
    //calculateEndpoints();
    calculateBorders();
}


/**
 * @todo deal with angles correctly
 */
void PF_Ellipse::mirror(PF_Vector axisPoint1, PF_Vector axisPoint2) {
    PF_Vector mp = data.center + data.majorP;

    data.center.mirror(axisPoint1, axisPoint2);
    mp.mirror(axisPoint1, axisPoint2);

    data.majorP = mp - data.center;

    double a = axisPoint1.angleTo(axisPoint2);

    PF_Vector vec;
    vec.setPolar(1.0, data.angle1);
    vec.mirror(PF_Vector(0.0,0.0), axisPoint2-axisPoint1);
    data.angle1 = vec.angle() - 2*a;

    vec.setPolar(1.0, data.angle2);
    vec.mirror(PF_Vector(0.0,0.0), axisPoint2-axisPoint1);
    data.angle2 = vec.angle() - 2*a;

    data.reversed = (!data.reversed);

    //calculateEndpoints();
    calculateBorders();
}



void PF_Ellipse::moveRef(const PF_Vector& ref, const PF_Vector& offset) {
	PF_Vector startpoint = getStartpoint();
	PF_Vector endpoint = getEndpoint();
	
    if (ref.distanceTo(startpoint)<1.0e-4) {
        moveStartpoint(startpoint+offset);
    }
    if (ref.distanceTo(endpoint)<1.0e-4) {
        moveEndpoint(endpoint+offset);
    }
}


void PF_Ellipse::draw(PF_Painter* painter, PF_GraphicView* view, double /*patternOffset*/) {

    if (painter==NULL || view==NULL) {
        return;
    }


    if (getPen().getLineType()==LX_Define::SolidLine ||
            isSelected() ||
            view->getDrawingMode()==LX_Define::ModePreview) {

        painter->drawEllipse(view->toGui(getCenter()),
                             getMajorRadius() * view->getFactor().x,
                             getMinorRadius() * view->getFactor().x,
                             getAngle(),
                             getAngle1(), getAngle2(),
                             isReversed());
    } else {
    	double styleFactor = getStyleFactor(view);
		if (styleFactor<0.0) {
        	painter->drawEllipse(view->toGui(getCenter()),
                             getMajorRadius() * view->getFactor().x,
                             getMinorRadius() * view->getFactor().x,
                             getAngle(),
                             getAngle1(), getAngle2(),
                             isReversed());
			return;
		}
		
        // Pattern:
        PF_LinePattern* pat;
        if (isSelected()) {
            pat = &patternSelected;
        } else {
            pat = view->getPattern(getPen().getLineType());
        }

        if (pat==NULL) {
            return;
        }

        // Pen to draw pattern is always solid:
        PF_Pen pen = painter->getPen();
        pen.setLineType(LX_Define::SolidLine);
        painter->setPen(pen);

        double* da;     // array of distances in x.
        int i;          // index counter

        double length = getAngleLength();

        // create pattern:
        da = new double[pat->num];

        double tot=0.0;
        i=0;
        bool done = false;
        double curA = getAngle1();
        double curR;
        PF_Vector cp = view->toGui(getCenter());
        double r1 = getMajorRadius() * view->getFactor().x;
        double r2 = getMinorRadius() * view->getFactor().x;

        do {
            curR = sqrt(PF_Math::pow(getMinorRadius()*cos(curA), 2.0)
                        + PF_Math::pow(getMajorRadius()*sin(curA), 2.0));

            if (curR>1.0e-6) {
                da[i] = fabs(pat->pattern[i] * styleFactor) / curR;
                if (pat->pattern[i] * styleFactor > 0.0) {

                    if (tot+fabs(da[i])<length) {
                        painter->drawEllipse(cp,
                                             r1, r2,
                                             getAngle(),
                                             curA,
                                             curA + da[i],
                                             false);
                    } else {
                        painter->drawEllipse(cp,
                                             r1, r2,
                                             getAngle(),
                                             curA,
                                             getAngle2(),
                                             false);
                    }
                }
            }
            curA+=da[i];
            tot+=fabs(da[i]);
            done=tot>length;

            i++;
            if (i>=pat->num) {
                i=0;
            }
        } while(!done);

        delete[] da;
    }
}



/**
 * Dumps the point's data to stdout.
 */
std::ostream& operator << (std::ostream& os, const PF_Ellipse& a) {
    os << " Ellipse: " << a.data << "\n";
    return os;
}

